//
//  jvm.m
//  iCodeMacRun
//
//  Created by Andrew Barry on 26/04/09.
//  Copyright 2009 __MyCompanyName__. All rights reserved.
//

#include <sys/mman.h>
#import "jvm.h"
#import "JavascriptProject.h"
#import "jsruntimecore.h"
#import "jsruntime.h"

#define debugLog(x)

static jvm *gjvm;

@implementation jvm
+(jvm *) instance {
	if (!gjvm) {
		gjvm = [jvm new];
		[gjvm startupJava];
	}
	return gjvm;
}

-(void)startupJava {
    int result = 0;
    JavaVM* theVM;

    /* default vm args */
    JavaVMInitArgs  vm_args;
	JavaVMOption options[2];
	
#ifdef CONSOLE
	NSString *rhinoPath = @"rhino.jar";
	NSString *moonPath = @"moon.jar";
	NSString *optionString = [NSString stringWithFormat:@"-Djava.class.path=%@:%@", moonPath, rhinoPath];
#else
	NSString *rhinoPath = [[NSBundle mainBundle] pathForResource:@"rhino" ofType:@"jar"];
	NSString *moonPath = [[NSBundle mainBundle] pathForResource:@"moon" ofType:@"jar"];
//	NSString *yuicompressPath = [[NSBundle mainBundle] pathForResource:@"yuicompress" ofType:@"jar"];
	NSString *dblayerPath = [[NSBundle mainBundle] pathForResource:@"dblayer" ofType:@"jar"];
	NSString *optionString = [NSString stringWithFormat:@"-Djava.class.path=%@:%@:%@", moonPath, rhinoPath, dblayerPath];
#endif

	options[0].optionString = [optionString UTF8String];
	
    vm_args.version  = JNI_VERSION_1_4;
	JNI_GetDefaultJavaVMInitArgs(&vm_args);
    vm_args.options  = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized  = JNI_TRUE;

    if (0 == JNI_CreateJavaVM(&theVM, (void**)&env, &vm_args))
		debugLog("createVM succeeded");
	else
		debugLog("createVM failed");
			 
//	jclass jdbcDriverManager = (*env)->FindClass(env, "java/sql/DriverManager");

	

	/*
	jclass driverClass = (*env)->FindClass(env, "org/hsqldb/jdbcDriver");

	jclass jdbcDriverManager = (*env)->FindClass(env, "java/sql/DriverManager");

	jstring connectionString = (*env)->NewStringUTF(env, "jdbc:hsqldb:/mydb");
	jstring username = (*env)->NewStringUTF(env, "sa");;
	jstring password = (*env)->NewStringUTF(env, "");;
	
    jmethodID getConnection = (*env)->GetStaticMethodID(env, jdbcDriverManager, "getConnection",
                                                 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/sql/Connection;");

	jobject conn = (*env)->CallStaticObjectMethod(env, jdbcDriverManager, getConnection, connectionString, username, password);
*/
}

-(NSString *) compressJavascript: (NSString *) script {
	if (!yuicompressor) {
		yuicompressor = (*env)->FindClass(env, "com/yahoo/platform/yui/compressor/YUICompressor");
		compressJS = (*env)->GetStaticMethodID(env, yuicompressor, "compressJavascript",
													 "(Ljava/lang/String;)Ljava/lang/String;");
	}
	
	jstring test = (*env)->NewStringUTF(env, [script UTF8String]);
	
	jstring str = (*env)->CallStaticObjectMethod(env, yuicompressor, compressJS, test);
	
	if (str) {
		int len = (*env)->GetStringLength(env, str);
		const jchar *text = (*env)->GetStringChars(env, str, nil);
		
		NSString *result = [NSString stringWithCharacters: text length: len];
		
		(*env)->ReleaseStringChars(env, str, text);
		return result;
	} else {
		return nil;
	}
}

-(void) startCompilation: (BOOL) forRelease {
	jclass compiler;
	jmethodID compilerConstructor;
	
	(*env)->PushLocalFrame(env, 10);

	compiler = (*env)->FindClass(env, "com/bcs/compiler/TargetSourceC");
	compilerConstructor = (*env)->GetMethodID(env, compiler, "<init>", "()V");
	
	compilerObject = (*env)->NewObject(env, compiler, compilerConstructor);

	if (forRelease) {
		jmethodID releaseMethod = (*env)->GetMethodID(env, compiler, "setRelease", "()V");
		(*env)->CallVoidMethod(env, compilerObject, releaseMethod);
	}
	
	jmethodID compilerInitialize = (*env)->GetMethodID(env, compiler, "initialize", "()V");
	(*env)->CallVoidMethod(env, compilerObject, compilerInitialize);

	compilerObject = (*env)->NewGlobalRef(env, compilerObject);
	
	(*env)->PopLocalFrame(env, nil);
}

-(void) startDirectCompilation: (BOOL) forRelease {
	jclass compiler;
	jmethodID compilerConstructor;
	
	(*env)->PushLocalFrame(env, 10);
	
	debugLog("find assembler target");
	
	compiler = (*env)->FindClass(env, "com/bcs/compiler/TargetAssemblerX86");

	
	debugLog("find assembler target constructor");

	compilerConstructor = (*env)->GetMethodID(env, compiler, "<init>", "()V");

	debugLog("new assembler target constructor");

	compilerObject = (*env)->NewObject(env, compiler, compilerConstructor);
	
	if (forRelease) {
		debugLog("tag assembler target for release");

		jmethodID releaseMethod = (*env)->GetMethodID(env, compiler, "setRelease", "()V");
		(*env)->CallVoidMethod(env, compilerObject, releaseMethod);
	}

	debugLog("new assembler target initialize");

	jmethodID compilerInitialize = (*env)->GetMethodID(env, compiler, "initialize", "()V");
	(*env)->CallVoidMethod(env, compilerObject, compilerInitialize);
	
	compilerObject = (*env)->NewGlobalRef(env, compilerObject);
	
	(*env)->PopLocalFrame(env, nil);
}

-(void) dumpLinkMap {
	jclass compiler;
	jmethodID dumpLinkMap;
	
	(*env)->PushLocalFrame(env, 10);
	
	compiler = (*env)->FindClass(env, "com/bcs/compiler/TargetAssemblerX86");
	dumpLinkMap = (*env)->GetMethodID(env, compiler, "dumpLinkMap", "()V");
	
	(*env)->CallVoidMethod(env, compilerObject, dumpLinkMap);
	
	(*env)->PopLocalFrame(env, nil);
}

-(BOOL) checkErrorsForProject: (JavascriptProject *) project {
	jclass compiler = (*env)->FindClass(env, "com/bcs/compiler/TargetAssemblerX86");

	jfieldID hasErrorFieldID = (*env)->GetFieldID(env, compiler, "hasError", "Z");
	jfieldID errorLineFieldID = (*env)->GetFieldID(env, compiler, "errorLine", "I");
	jfieldID errorMessageFieldID = (*env)->GetFieldID(env, compiler, "errorMessage", "Ljava/lang/String;");
	jfieldID errorScriptFieldID = (*env)->GetFieldID(env, compiler, "errorScript", "Ljava/lang/String;");
	
	if ((*env)->GetBooleanField(env, compilerObject, hasErrorFieldID)) {
		int errorLine = (*env)->GetIntField(env, compilerObject, errorLineFieldID);
		jstring errorMessage = (*env)->GetObjectField(env, compilerObject, errorMessageFieldID);
		int len = (*env)->GetStringLength(env, errorMessage);
		const jchar *msg = (*env)->GetStringChars(env, errorMessage, nil);
		NSString *errorString = [NSString stringWithCharacters:msg length:len];
		(*env)->ReleaseStringChars(env, errorMessage, msg);
		
		errorMessage = (*env)->GetObjectField(env, compilerObject, errorScriptFieldID);
		len = (*env)->GetStringLength(env, errorMessage);
		msg = (*env)->GetStringChars(env, errorMessage, nil);
		NSString *scriptString = [NSString stringWithCharacters:msg length:len];
		(*env)->ReleaseStringChars(env, errorMessage, msg);
		
		if (!project) {
			printf("Error on line %d\n", errorLine);
			exit(1);
		}
		[project displayCompilationError: errorString forFile: scriptString forControl: nil forEvent: nil atLine: errorLine];
		
		(*env)->PopLocalFrame(env, nil);
		return NO;
	}
	return YES;
}

-(struct LoadedCode *) getFullCodeForProject: (JavascriptProject *) project {
	jclass compiler;
	jmethodID getFullCode, getLinkMap;
	
	(*env)->PushLocalFrame(env, 10);
	
	compiler = (*env)->FindClass(env, "com/bcs/compiler/TargetAssemblerX86");
	getFullCode = (*env)->GetMethodID(env, compiler, "getFullCode", "()[B");
	getLinkMap = (*env)->GetMethodID(env, compiler, "getLinkMap", "()[B");
	
	jobject result = (*env)->CallObjectMethod(env, compilerObject, getFullCode);
	
	if ((*env)->ExceptionCheck(env)) {
		(*env)->ExceptionDescribe(env);
		return NO;
	}
	
	BOOL success = [self checkErrorsForProject:project];

	if (!success) {
		(*env)->PopLocalFrame(env, nil);
		return NO;
	}
	
	int length = (*env)->GetArrayLength(env, result);
	jbyte *data = (*env)->GetByteArrayElements(env, result, nil);

	unsigned char *buf = malloc(length);
	memcpy(buf, data, length);
	(*env)->ReleaseByteArrayElements(env, result, data, JNI_ABORT);

	// get link map
	jobject jlinkMap = (*env)->CallObjectMethod(env, compilerObject, getLinkMap);
	int maplength = (*env)->GetArrayLength(env, jlinkMap);
	jbyte *mapdata = (*env)->GetByteArrayElements(env, jlinkMap, nil);

	unsigned char *linkMap = malloc(maplength);
	memcpy(linkMap, mapdata, maplength);
	(*env)->ReleaseByteArrayElements(env, jlinkMap, mapdata, JNI_ABORT);
	(*env)->PopLocalFrame(env, nil);
	
	struct LoadedCode *rv = malloc(sizeof(struct LoadedCode));
	
	rv->code = buf;
	rv->codeLength = length;
	rv->linkMap = linkMap;
	rv->linkLength = maplength;
	
	return rv;
}

-(bool) compileAddFile: (NSString *) path forProject: (JavascriptProject *) project {
	(*env)->PushLocalFrame(env, 10);

	jclass compiler = (*env)->FindClass(env, "com/bcs/moon/XCompile");
	jmethodID compilerAddFile = (*env)->GetMethodID(env, compiler, "addFile", "(Ljava/lang/String;)V");
	
	jstring js_path = (*env)->NewStringUTF(env, [path UTF8String]);
	
	(*env)->CallVoidMethod(env, compilerObject, compilerAddFile, js_path);
	
	if ((*env)->ExceptionCheck(env)) {
		(*env)->ExceptionDescribe(env);
		return NO;
	}

	BOOL rv = [self checkErrorsForProject:project];
	
	(*env)->PopLocalFrame(env, nil);
	return rv;
}


-(bool) parseJavascript: (NSString *) script forSource: (NSString *) name forProject: (JavascriptProject *) project {
	jclass compiler = (*env)->FindClass(env, "com/bcs/moon/XCompile");
	jmethodID compilerParse = (*env)->GetMethodID(env, compiler, "parseScript", "(Ljava/lang/String;Ljava/lang/String;)V");
	
	jstring js_script = (*env)->NewStringUTF(env, [script UTF8String]);
	jstring js_name = (*env)->NewStringUTF(env, [name UTF8String]);
	
	(*env)->CallVoidMethod(env, compilerObject, compilerParse, js_name, js_script);

	jfieldID hasErrorFieldID = (*env)->GetFieldID(env, compiler, "hasError", "Z");
	jfieldID errorLineFieldID = (*env)->GetFieldID(env, compiler, "errorLine", "I");
	jfieldID errorMessageFieldID = (*env)->GetFieldID(env, compiler, "errorMessage", "Ljava/lang/String;");

	if ((*env)->GetBooleanField(env, compilerObject, hasErrorFieldID)) {
		int errorLine = (*env)->GetIntField(env, compilerObject, errorLineFieldID);
		jstring errorMessage = (*env)->GetObjectField(env, compilerObject, errorMessageFieldID);
		
		int len = (*env)->GetStringLength(env, errorMessage);
		const jchar *msg = (*env)->GetStringChars(env, errorMessage, nil);
		NSString *errorString = [NSString stringWithCharacters:msg length:len];
		(*env)->ReleaseStringChars(env, errorMessage, msg);
		[project displayCompilationError: errorString forFile: name forControl: nil forEvent: nil atLine: errorLine];
		return NO;
	}
	return YES;
}

-(bool) compileFunction: (NSString *) script withName: (NSString *) fnname forSource: (NSString *) name  forControl: (NSString *) ctlName forEvent: (NSString *) eventName forProject: (JavascriptProject *) project {
	jclass compiler = (*env)->FindClass(env, "com/bcs/moon/XCompile");
	jmethodID compilerParse = (*env)->GetMethodID(env, compiler, "compileFunction", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
	
	NSString *tag = [NSString stringWithFormat:@"%@/%@/%@", [name lastPathComponent], ctlName ? ctlName : @"_form", eventName];

	jstring js_script = (*env)->NewStringUTF(env, [script UTF8String]);
	jstring js_name = (*env)->NewStringUTF(env, [tag UTF8String]);
	jstring js_fnname = (*env)->NewStringUTF(env, [fnname UTF8String]);

	(*env)->CallVoidMethod(env, compilerObject, compilerParse, js_name, js_script, js_fnname);

	if ((*env)->ExceptionCheck(env)) {
		(*env)->ExceptionDescribe(env);
		return NO;
	}
	
	jfieldID hasErrorFieldID = (*env)->GetFieldID(env, compiler, "hasError", "Z");
	jfieldID errorLineFieldID = (*env)->GetFieldID(env, compiler, "errorLine", "I");
	jfieldID errorMessageFieldID = (*env)->GetFieldID(env, compiler, "errorMessage", "Ljava/lang/String;");
	
	if ((*env)->GetBooleanField(env, compilerObject, hasErrorFieldID)) {
		int errorLine = (*env)->GetIntField(env, compilerObject, errorLineFieldID);
		jstring errorMessage = (*env)->GetObjectField(env, compilerObject, errorMessageFieldID);
		
		int len = (*env)->GetStringLength(env, errorMessage);
		const jchar *msg = (*env)->GetStringChars(env, errorMessage, nil);
		NSString *errorString = [NSString stringWithCharacters:msg length:len];
		(*env)->ReleaseStringChars(env, errorMessage, msg);
		[project displayCompilationError: errorString forFile: name forControl: ctlName forEvent: eventName atLine: errorLine];
		return NO;
	}
	return YES;
}

-(bool) compileJavascript: (NSString *) path forProject: (JavascriptProject *) project {
	jclass compiler;
	compiler = (*env)->FindClass(env, "com/bcs/moon/XCompile");
	jmethodID compilerCompile = (*env)->GetMethodID(env, compiler, "compile", "(Ljava/lang/String;)V");
	jfieldID hasErrorFieldID = (*env)->GetFieldID(env, compiler, "hasError", "Z");
	jfieldID errorLineFieldID = (*env)->GetFieldID(env, compiler, "errorLine", "I");
	jfieldID errorMessageFieldID = (*env)->GetFieldID(env, compiler, "errorMessage", "Ljava/lang/String;");
	jfieldID errorScriptFieldID = (*env)->GetFieldID(env, compiler, "errorScript", "Ljava/lang/String;");

	jstring destPath = (*env)->NewStringUTF(env, [path UTF8String]);
	
	(*env)->CallVoidMethod(env, compilerObject, compilerCompile, destPath);
	if ((*env)->ExceptionCheck(env)) {
		(*env)->ExceptionDescribe(env);
		return NO;
	}
	
	if ((*env)->GetBooleanField(env, compilerObject, hasErrorFieldID)) {
		int errorLine = (*env)->GetIntField(env, compilerObject, errorLineFieldID);
		jstring errorMessage = (*env)->GetObjectField(env, compilerObject, errorMessageFieldID);
		int len = (*env)->GetStringLength(env, errorMessage);
		const jchar *msg = (*env)->GetStringChars(env, errorMessage, nil);
		NSString *errorString = [NSString stringWithCharacters:msg length:len];
		(*env)->ReleaseStringChars(env, errorMessage, msg);
		
		errorMessage = (*env)->GetObjectField(env, compilerObject, errorScriptFieldID);
		len = (*env)->GetStringLength(env, errorMessage);
		msg = (*env)->GetStringChars(env, errorMessage, nil);
		NSString *scriptString = [NSString stringWithCharacters:msg length:len];
		(*env)->ReleaseStringChars(env, errorMessage, msg);
				
		NSArray *bits = [scriptString componentsSeparatedByString:@"/"];

		if ([bits count] == 3)
			[project displayCompilationError: errorString forFile: [project pathTo:[bits objectAtIndex:0]] forControl: [bits objectAtIndex:1] forEvent: [bits objectAtIndex:2] atLine: errorLine];
		else
			[project displayCompilationError: errorString forFile: [project pathTo: scriptString] forControl: nil forEvent: nil atLine: errorLine];

		return NO;
	}

	return YES;
}


@end
